<?php

/**
 * ShopEx licence
 *
 * @copyright  Copyright (c) 2005-2012 ShopEx Technologies Inc. (http://www.shopex.cn)
 * @license  http://ecos.shopex.cn/ ShopEx License
 */

/**
 * alipay notify 异步验证接口
 * @auther shopex ecstore dev dev@shopex.cn
 * @version 0.1
 * @package ectools.lib.payment.plugin
 */
class wap_payment_plugin_malipay_server extends ectools_payment_app {

    /**
     * @支付宝固定参数
     */
    public $sec_id = 'MD5';    //签名方式 不需修改
    public $_input_charset = 'utf-8';    //字符编码格式
    public $_input_charset_GBK = "GBK";
    public $v = '2.0';    //版本号
    public $gateway_paychannel = "https://mapi.alipay.com/cooperate/gateway.do?";
    public $gateway = "http://wappaygw.alipay.com/service/rest.htm?";

    /**
     * 支付后返回后处理的事件的动作
     * @params array - 所有返回的参数，包括POST和GET
     * @return null
     */
    public function callback(&$recv) {
        #键名与pay_setting中设置的一致
        $mer_id = trim($this->getConf('mer_id', substr(__CLASS__, 0, strrpos(__CLASS__, '_'))));
        $mer_key = trim($this->getConf('mer_key', substr(__CLASS__, 0, strrpos(__CLASS__, '_'))));

        if ($this->is_return_vaild($recv, $mer_key, $this->sec_id)) {
            $rec = kernel::single('site_utility_xml')->xml2array($recv['notify_data']);
            $ret['payment_id'] = $rec['notify']['out_trade_no'];
            $ret['account'] = $mer_id;
            $ret['bank'] = app::get('wap')->_('手机支付宝');
            $ret['pay_account'] = app::get('wap')->_('付款帐号');
            $ret['currency'] = 'CNY';
            $ret['money'] = $rec['notify']['total_fee'];
            $ret['paycost'] = '0.000';
            $ret['cur_money'] = $rec['notify']['total_fee'];
            $ret['trade_no'] = $rec['notify']['trade_no'];
            $ret['t_payed'] = strtotime($rec['notify']['notify_time']) ? strtotime($rec['notify']['notify_time']) : time();
            $ret['pay_app_id'] = "malipay";
            $ret['pay_type'] = 'online';
            $ret['memo'] = '';

            $status = $rec['notify']['trade_status'];        //返回token
            if ($status == 'TRADE_FINISHED' || $status == 'TRADE_SUCCESS') {
                echo "success";
                $ret['status'] = 'succ';
            } else {
                echo "fail";
                $ret['status'] = 'failed';
            }
        } else {
            $ret['message'] = 'Invalid Sign';
            $ret['status'] = 'invalid';
        }
        return $ret;
    }

    /**
     * 支付宝回调接口
     * 
     * @param array $recv
     * 
     * @return array 回调信息
     * 
     * @author AlanPi <pijian@shopexunit.com>
     */
    public function callback1(&$recv) {
        // 日志记录支付宝回调信息
        logger::error("malipay_callback:\n" . var_export($recv, 1));

//        // $mer_key 这里似乎没有用到
//        $mer_key = trim($this->getConf('mer_key', substr(__CLASS__, 0, strrpos(__CLASS__, '_'))));

        if ($this->_getSignVeryfy($recv, $recv['sign'])) {
            $ret = $this->_getRet($recv);

            $status = $recv['trade_status'];        //返回token
            if ($status == 'TRADE_FINISHED' || $status == 'TRADE_SUCCESS') {
                // 组装 $sdf
                $bill = app::get('ectools')->model('order_bills')->getRow('*', array('bill_id' => $ret['payment_id']));
                $payment = app::get('ectools')->model('payments')->getRow('*', array('payment_id' => $ret['payment_id']));
                $sdf = $payment;
                $sdf['orders'][$bill['rel_id']] = $bill;

                // 使用 order.pay_finish service 处理回调订单和预存款、加盟费等信息
                $obj_pay_lists = kernel::servicelist("order.pay_finish");
                $is_payed = false;
                foreach ($obj_pay_lists as $order_pay_service_object) {
                    $is_payed = $order_pay_service_object->order_pay_finish($sdf, 'succ', 'font', $msg);
                }

                echo "success";
                $ret['status'] = 'succ';
            } else {
                echo "fail";
                $ret['status'] = 'failed';
            }
        } else {
            $ret['message'] = 'Invalid Sign';
            $ret['status'] = 'invalid';
        }

        // 日志记录支付宝回调是否成功
        logger::error("malipay_callback:\n" . var_export($ret, 1));

        return $ret;
    }

    /**
     * 获取组装好的返回参数
     * 
     * @param array $ret
     * @param array $recv
     * 
     * @return array
     * 
     * @author AlanPi <pijian@shopexunit.com>
     */
    private function _getRet($recv, $ret = array()) {
        // 键名与pay_setting中设置的一致
        $mer_id = trim($this->getConf('mer_id', substr(__CLASS__, 0, strrpos(__CLASS__, '_'))));

        $ret['payment_id'] = $recv['out_trade_no'];
        $ret['account'] = $mer_id;
        $ret['bank'] = app::get('wap')->_('手机支付宝');
        $ret['pay_account'] = app::get('wap')->_('付款帐号');
        $ret['currency'] = 'CNY';
        $ret['money'] = $recv['total_fee'];
        $ret['paycost'] = '0.000';
        $ret['cur_money'] = $recv['total_fee'];
        $ret['trade_no'] = $recv['trade_no'];
        $ret['t_payed'] = strtotime($recv['notify_time']) ? strtotime($recv['notify_time']) : time();
        $ret['pay_app_id'] = "malipay";
        $ret['pay_type'] = 'online';
        $ret['memo'] = '';

        return $ret;
    }

    /**
     * 检验返回数据合法性
     * @param mixed $form 包含签名数据的数组
     * @param mixed $key 签名用到的私钥
     * @access private
     * @return boolean
     */
    public function is_return_vaild($form, $key, $secu_id) {
        $_key = $key;
        $sign_type = $secu_id;
        //此处为固定顺序，支付宝Notify返回消息通知比较特殊，这里不需要升序排列
        $notifyarray = array(
            "service" => $form['service'],
            "v" => $form['v'],
            "sec_id" => $form['sec_id'],
            "notify_data" => $form['notify_data']
        );
        $mysign = $this->_build_mysign($notifyarray, $_key, $sign_type);

        if ($form['sign'] == $mysign) {
            return true;
        }
        #记录返回失败的情况	
        logger::error(app::get('wap')->_('支付单号：') . $form['out_trade_no'] . app::get('wap')->_('签名验证不通过，请确认！') . "\n");
        logger::error(app::get('wap')->_('本地产生的加密串：') . $mysign);
        logger::error(app::get('wap')->_('手机支付宝传递打过来的签名串：') . $form['sign']);
        $str_xml .= "<alipayform>";
        foreach ($form as $key => $value) {
            $str_xml .= "<$key>" . $value . "</$key>";
        }
        $str_xml .= "</alipayform>";

        return false;
    }

    /**
     * 生成签名结果
     * $array要签名的数组
     * return 签名结果字符串
     */
    private function _build_mysign($sort_array, $key, $sign_type = "MD5") {
        $prestr = $this->_create_linkstring($sort_array);         //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $prestr . $key;                            //把拼接后的字符串再与安全校验码直接连接起来
        $mysgin = $this->_sign($prestr, $sign_type);                //把最终的字符串签名，获得签名结果
        return $mysgin;
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * $array 需要拼接的数组
     * return 拼接完成以后的字符串
     */
    private function _create_linkstring($array) {
        $arg = "";
        while (list ($key, $val) = each($array)) {
            $arg.=$key . "=" . $val . "&";
        }
        $arg = substr($arg, 0, count($arg) - 2);             //去掉最后一个&字符
        return $arg;
    }

    /**
     * 对数组排序
     * 
     * @param array $array 排序前的数组
     * 
     * @return array 排序后的数组
     */
    private function _arg_sort($array) {
        ksort($array);
        reset($array);

        return $array;
    }

    /**
     * 签名字符串
     * $prestr 需要签名的字符串
     * $sign_type 签名类型，也就是sec_id
     * return 签名结果
     */
    private function _sign($prestr, $sign_type) {
        $sign = '';
        if ($sign_type == 'MD5') {
            $sign = md5($prestr);
        } elseif ($sign_type == 'DSA') {
            //DSA 签名方法待后续开发
            die("DSA 签名方法待后续开发，请先使用MD5签名方式");
        } else {
            die("支付宝暂不支持" . $sign_type . "类型的签名方式");
        }
        return $sign;
    }

    /**
     * 通过节点路径返回字符串的某个节点值
     * $res_data——XML 格式字符串
     * 返回节点参数
     */
    private function getDataForXML($res_data, $node) {
        $xml = simplexml_load_string($res_data);
        $result = $xml->xpath($node);

        while (list(, $node) = each($result)) {
            return $node;
        }
    }

    /**
     * 获取返回时的签名验证结果
     * 
     * @param array $para_temp 通知返回来的参数数组
     * @param string $sign 返回的签名结果
     * 
     * @return boolean 签名验证结果
     * 
     * @author AlanPi <pijian@shopexunit.com>
     */
    private function _getSignVeryfy($para_temp, $sign) {
        // 注意：这里只是一个测试用的公钥，并非正式公钥，一定要记得替换，或者是改为可配置
//        $ali_public_key_path = __DIR__ . '/alipay_public_key.pem';
        $ali_public_key_path = DATA_DIR . '/cert/payment_plugin_malipay/alipay_public_key.pem';

        //除去待签名参数数组中的空值和签名参数
        $para_filter = $this->_paraFilter($para_temp);

        //对待签名参数数组排序
        $para_sort = $this->_arg_sort($para_filter);

        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->_create_linkstring($para_sort);

        $isSgin = false;

        switch (strtoupper(trim($para_temp['sign_type']))) {
            case "RSA" :
                $isSgin = $this->_rsaVerify($prestr, trim($ali_public_key_path), $sign);
                break;
            default :
                $isSgin = false;
        }

        return $isSgin;
    }

    /**
     * 除去数组中的空值和签名参数
     * 
     * @param array $para 签名参数组
     * 
     * @return array 去掉空值与签名参数后的新签名参数组
     * 
     * @author AlanPi <pijian@shopexunit.com>
     */
    private function _paraFilter($para) {
        $para_filter = array();
        while (list ($key, $val) = each($para)) {
            if ($key == "sign" || $key == "sign_type" || $val == "") {
                continue;
            } else {
                $para_filter[$key] = $para[$key];
            }
        }
        return $para_filter;
    }

    /**
     * RSA验签
     * 
     * @param string $data 待签名数据
     * @param string $ali_public_key_path 支付宝的公钥文件路径
     * @param string $sign 要校对的的签名结果
     * 
     * @return boolean 验证结果
     * 
     * @author AlanPi <pijian@shopexunit.com>
     */
    private function _rsaVerify($data, $ali_public_key_path, $sign) {
        $pubKey = file_get_contents($ali_public_key_path);

        $res = openssl_get_publickey($pubKey);
        $result = (bool) openssl_verify($data, base64_decode($sign), $res);
        openssl_free_key($res);

        return $result;
    }

}
